﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.IO;

namespace SimpleReminder
{
    public class MyEvents
    {
        private static DataSet _dsEvents = null;
        
        private static DataTable _dtEvents = null;
        public static DataTable EventsTable
        {
            get { return _dtEvents; }
        }

        const string KEY_EVENTS = "Events";
        const string EVENTS_DB = KEY_EVENTS + ".db";
        const string EVENTS_DB_BAK = EVENTS_DB + ".bak";

        public enum EventTypes
        {
            Secondly,
            Minutely,
            Hourly,
            Daily,
            Weekly,
            Monthly,
            Yearly,
            Once,
            Startup,
            None
        }

        public enum EventStatus
        {
            Active,
            Completed,
            Disabled
        }

        public const string COL_ID = "Id";
        public const string COL_NAME = "Name";
        public const string COL_DESCRIPTION = "Description";
        public const string COL_TYPE = "Type";
        public const string COL_STATUS = "Status";
        public const string COL_STARTDATE = "StartDate";
        public const string COL_ENDDATE = "EndDate";
        public const string COL_SCHEDULE = "Schedule";
        public const string COL_LASTRUNTIME = "LastRunTime";
        public const string COL_LASTLASTRUNTIME = "LastLastRunTime";
        
        public const string COL_REMINDER = "Reminder";

        public const string COL_FREQUENCE = "Frequence";

        public const string COL_WEEKLYDAY_MON = "WeeklyDayMon";
        public const string COL_WEEKLYDAY_TUE = "WeeklyDayTue";
        public const string COL_WEEKLYDAY_WED = "WeeklyDayWed";
        public const string COL_WEEKLYDAY_THU = "WeeklyDayThu";
        public const string COL_WEEKLYDAY_FRI = "WeeklyDayFri";
        public const string COL_WEEKLYDAY_SAT = "WeeklyDaySat";
        public const string COL_WEEKLYDAY_SUN = "WeeklyDaySun";

        public const string COL_MONTHLY_TYPE1 = "MonthlyType1";
        public const string COL_MONTHLY_TYPE2 = "MonthlyType2";

        public const string COL_MONTHLY_DAY = "MonthlyDay";
        public const string COL_MONTHLY_WEEKNO = "MonthlyWeekNo";
        public const string COL_MONTHLY_WEEKDAY = "MonthlyWeekDay";

        public const string COL_MONTHLY_JAN = "MonthlyJan";
        public const string COL_MONTHLY_FEB = "MonthlyFeb";
        public const string COL_MONTHLY_MAR = "MonthlyMar";
        public const string COL_MONTHLY_APR = "MonthlyApr";
        public const string COL_MONTHLY_MAY = "MonthlyMay";
        public const string COL_MONTHLY_JUN = "MonthlyJun";
        public const string COL_MONTHLY_JUL = "MonthlyJul";
        public const string COL_MONTHLY_AUG = "MonthlyAug";
        public const string COL_MONTHLY_SEP = "MonthlySep";
        public const string COL_MONTHLY_OCT = "MonthlyOct";
        public const string COL_MONTHLY_NOV = "MonthlyNov";
        public const string COL_MONTHLY_DEC = "MonthlyDec";

        public const string COL_AGENT_PATH = "AgentPath";
        public const string COL_AGENT_ANIMATION = "AgentAnimation";
        public const string COL_AGENT_TOP = "AgentTop";
        public const string COL_AGENT_LEFT = "AgentLeft";

        public const string COL_EMAIL_TO = "EmailTo";
        public const string COL_EMAIL_CC = "EmailCc";
        public const string COL_EMAIL_BCC = "EmailBcc";

        public const string COL_APP_PATH = "AppPath";
        public const string COL_APP_ARG = "AppArg";

        public const string MONTHLY_WEEK_FIRST = "first";
        public const string MONTHLY_WEEK_SECOND = "second";
        public const string MONTHLY_WEEK_THIRD = "third";
        public const string MONTHLY_WEEK_FOURTH = "fourth";
        public const string MONTHLY_WEEK_LAST = "last";

        public const string MONDAY = "Mon";
        public const string TUESDAY = "Tue";
        public const string WEDNESDAY = "Wed";
        public const string THURSDAY = "Thu";
        public const string FRIDAY = "Fri";
        public const string SATURDAY = "Sat";
        public const string SUNDAY = "Sun";

        public const string REMINDER_AGENT = "Microsoft Agent";
        public const string REMINDER_EMAIL = "Email";
        public const string REMINDER_APP = "External Application";

        private static void CreateEventsTable()
        {
            _dtEvents = new DataTable(KEY_EVENTS);
            
            _dtEvents.Columns.Add(COL_ID, typeof(String));
            _dtEvents.Columns.Add(COL_NAME, typeof(String));
            _dtEvents.Columns.Add(COL_DESCRIPTION, typeof(String));
            _dtEvents.Columns.Add(COL_TYPE, typeof(String));
            _dtEvents.Columns.Add(COL_STATUS, typeof(String));
            _dtEvents.Columns.Add(COL_STARTDATE, typeof(DateTime));
            _dtEvents.Columns.Add(COL_ENDDATE, typeof(DateTime));
            _dtEvents.Columns.Add(COL_SCHEDULE, typeof(String));
            _dtEvents.Columns.Add(COL_LASTRUNTIME, typeof(DateTime));
            _dtEvents.Columns.Add(COL_LASTLASTRUNTIME, typeof(DateTime));

            _dtEvents.Columns.Add(COL_REMINDER, typeof(String));

            _dtEvents.Columns.Add(COL_FREQUENCE, typeof(int));

            _dtEvents.Columns.Add(COL_WEEKLYDAY_MON, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_TUE, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_WED, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_THU, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_FRI, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_SAT, typeof(Boolean));
            _dtEvents.Columns.Add(COL_WEEKLYDAY_SUN, typeof(Boolean));

            _dtEvents.Columns.Add(COL_MONTHLY_TYPE1, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_TYPE2, typeof(Boolean));

            _dtEvents.Columns.Add(COL_MONTHLY_DAY, typeof(int));
            _dtEvents.Columns.Add(COL_MONTHLY_WEEKNO, typeof(string));
            _dtEvents.Columns.Add(COL_MONTHLY_WEEKDAY, typeof(string));

            _dtEvents.Columns.Add(COL_MONTHLY_JAN, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_FEB, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_MAR, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_APR, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_MAY, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_JUN, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_JUL, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_AUG, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_SEP, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_OCT, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_NOV, typeof(Boolean));
            _dtEvents.Columns.Add(COL_MONTHLY_DEC, typeof(Boolean));

            _dtEvents.Columns.Add(COL_AGENT_PATH, typeof(String));
            _dtEvents.Columns.Add(COL_AGENT_ANIMATION, typeof(String));
            _dtEvents.Columns.Add(COL_AGENT_TOP, typeof(int));
            _dtEvents.Columns.Add(COL_AGENT_LEFT, typeof(int));

            _dtEvents.Columns.Add(COL_EMAIL_TO, typeof(String));
            _dtEvents.Columns.Add(COL_EMAIL_CC, typeof(String));
            _dtEvents.Columns.Add(COL_EMAIL_BCC, typeof(String));

            _dtEvents.Columns.Add(COL_APP_PATH, typeof(String));
            _dtEvents.Columns.Add(COL_APP_ARG, typeof(String));

            _dtEvents.PrimaryKey = new DataColumn[] {
                   _dtEvents.Columns[COL_ID]
                };

            _dsEvents.Tables.Add(_dtEvents);
        }

        public static DataRow NewEvent()
        {
            DataRow dr = _dtEvents.NewRow();

            dr[COL_ID] = Guid.NewGuid().ToString();
            dr[COL_TYPE] = MyEvents.EventTypes.Once.ToString();
            dr[COL_STATUS] = MyEvents.EventStatus.Active.ToString();
            dr[COL_NAME] = "New Event";
            dr[COL_DESCRIPTION] = "New Event";
            dr[COL_STARTDATE] = DateTime.Today;
            dr[COL_ENDDATE] = System.Windows.Forms.DateTimePicker.MaximumDateTime;
            dr[COL_SCHEDULE] = GetSchedule(dr);
            dr[COL_LASTRUNTIME] = System.Windows.Forms.DateTimePicker.MinimumDateTime;
            dr[COL_LASTLASTRUNTIME] = dr[COL_LASTRUNTIME];

            dr[COL_REMINDER] = REMINDER_AGENT;

            dr[COL_FREQUENCE] = 1;

            dr[COL_WEEKLYDAY_MON] = true;
            dr[COL_WEEKLYDAY_TUE] = false;
            dr[COL_WEEKLYDAY_WED] = false;
            dr[COL_WEEKLYDAY_THU] = false;
            dr[COL_WEEKLYDAY_FRI] = false;
            dr[COL_WEEKLYDAY_SAT] = false;
            dr[COL_WEEKLYDAY_SUN] = false;

            dr[COL_MONTHLY_TYPE1] = true;
            dr[COL_MONTHLY_TYPE2] = false;

            dr[COL_MONTHLY_DAY] = 1;
            dr[COL_MONTHLY_WEEKNO] = MONTHLY_WEEK_FIRST;
            dr[COL_MONTHLY_WEEKDAY] = MONDAY;

            dr[COL_MONTHLY_JAN] = true;
            dr[COL_MONTHLY_FEB] = true;
            dr[COL_MONTHLY_MAR] = true;
            dr[COL_MONTHLY_APR] = true;
            dr[COL_MONTHLY_MAY] = true;
            dr[COL_MONTHLY_JUN] = true;
            dr[COL_MONTHLY_JUL] = true;
            dr[COL_MONTHLY_AUG] = true;
            dr[COL_MONTHLY_SEP] = true;
            dr[COL_MONTHLY_OCT] = true;
            dr[COL_MONTHLY_NOV] = true;
            dr[COL_MONTHLY_DEC] = true;

            dr[COL_AGENT_PATH] = String.Empty;
            dr[COL_AGENT_ANIMATION] = String.Empty;
            dr[COL_AGENT_TOP] = 0;
            dr[COL_AGENT_LEFT] = 0;

            dr[COL_EMAIL_TO] = String.Empty;
            dr[COL_EMAIL_CC] = String.Empty;
            dr[COL_EMAIL_BCC] = String.Empty;

            dr[COL_APP_PATH] = String.Empty;
            dr[COL_APP_ARG] = String.Empty;

            _dtEvents.Rows.Add(dr);
            return dr;
        }

        public static string GetWeeklyDays(DataRow dr)
        {
            bool mon = (bool) dr[COL_WEEKLYDAY_MON];
            bool tue = (bool)dr[COL_WEEKLYDAY_TUE];
            bool wed = (bool)dr[COL_WEEKLYDAY_WED];
            bool thu = (bool)dr[COL_WEEKLYDAY_THU];
            bool fri = (bool)dr[COL_WEEKLYDAY_FRI];
            bool sat = (bool)dr[COL_WEEKLYDAY_SAT];
            bool sun = (bool)dr[COL_WEEKLYDAY_SUN];

            StringBuilder sb = new StringBuilder();
            if (mon) sb.Append("Mon, ");
            if (tue) sb.Append("Tue, ");
            if (wed) sb.Append("Wed, ");
            if (thu) sb.Append("Thu, ");
            if (fri) sb.Append("Fri, ");
            if (sat) sb.Append("Sat, ");
            if (sun) sb.Append("Sun, ");
            
            if(sb.Length>2) sb.Remove(sb.Length - 2, 2);
            return sb.ToString();
        }

        public static string GetMonthlyType(DataRow dr)
        {
            bool type1 = (bool)dr[COL_MONTHLY_TYPE1];
            string monthlyType = String.Empty;
            if (type1)
            {
                monthlyType = String.Format("day {0}", dr[COL_MONTHLY_DAY]);
            }
            else
            {
                monthlyType = String.Format("the {0} {1}", dr[COL_MONTHLY_WEEKNO], dr[COL_MONTHLY_WEEKDAY]);
            }
            return monthlyType;
        }

        public static string GetMonthlyMonths(DataRow dr)
        {
            bool jan = (bool)dr[COL_MONTHLY_JAN];
            bool feb = (bool)dr[COL_MONTHLY_FEB];
            bool mar = (bool)dr[COL_MONTHLY_MAR];
            bool apr = (bool)dr[COL_MONTHLY_APR];
            bool may = (bool)dr[COL_MONTHLY_MAY];
            bool jun = (bool)dr[COL_MONTHLY_JUN];
            bool jul = (bool)dr[COL_MONTHLY_JUL];
            bool aug = (bool)dr[COL_MONTHLY_AUG];
            bool sep = (bool)dr[COL_MONTHLY_SEP];
            bool oct = (bool)dr[COL_MONTHLY_OCT];
            bool nov = (bool)dr[COL_MONTHLY_NOV];
            bool dec = (bool)dr[COL_MONTHLY_DEC];

            if (jan && feb && mar && apr && may && jun && jul && aug && sep && oct && nov && dec)
            {
                return "every month";
            }

            StringBuilder sb = new StringBuilder();
            if (jan) sb.Append("Jan, ");
            if (feb) sb.Append("Feb, ");
            if (mar) sb.Append("Mar, ");
            if (apr) sb.Append("Apr, ");
            if (may) sb.Append("May, ");
            if (jun) sb.Append("Jun, ");
            if (jul) sb.Append("Jul, ");
            if (aug) sb.Append("Aug, ");
            if (sep) sb.Append("Sep, ");
            if (oct) sb.Append("Oct, ");
            if (nov) sb.Append("Nov, ");
            if (dec) sb.Append("Dec, ");

            if (sb.Length > 2) sb.Remove(sb.Length - 2, 2);
            return sb.ToString();
        }

        public static string GetSchedule(DataRow dr)
        {
            MyEvents.EventTypes et = Utils.ParseEventType(dr[COL_TYPE] as string);
            string schedule = null;
            switch (et)
            {
                case EventTypes.Once:
                    schedule = String.Format("At {0:HH:mm} on {1:yyyy-MM-dd}",
                        dr[COL_STARTDATE], dr[COL_STARTDATE]);
                    break;
                case EventTypes.Startup:
                    schedule = "Run at application startup";
                    break;
                case EventTypes.Secondly:
                    schedule = String.Format("Every {0} second{1}, starting at {2:yyyy-MM-dd HH:mm}",
                        dr[COL_FREQUENCE], Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    break;
                case EventTypes.Minutely:
                    schedule = String.Format("Every {0} minute{1}, starting at {2:yyyy-MM-dd HH:mm}",
                        dr[COL_FREQUENCE], Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    break;
                case EventTypes.Daily:
                    schedule = String.Format("At {0:HH:mm} every {1} day{2}, starting {3:yyyy-MM-dd}",
                        dr[COL_STARTDATE], dr[COL_FREQUENCE], Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    break;
                case EventTypes.Weekly:
                    string weeklyDays = GetWeeklyDays(dr);
                    if (String.IsNullOrEmpty(weeklyDays))
                    {
                        schedule = String.Empty;
                    }
                    else
                    {
                        schedule = String.Format("At {0:HH:mm} every {1} of every {2} week{3}, starting {4:yyyy-MM-dd}",
                            dr[COL_STARTDATE], weeklyDays, dr[COL_FREQUENCE],
                            Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    }
                    break;
                case EventTypes.Hourly:
                    schedule = String.Format("Every {0} hour{1}, starting {2:yyyy-MM-dd HH:mm}",
                        dr[COL_FREQUENCE], Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    break;
                case EventTypes.Yearly:
                    schedule = String.Format("Every {0} year{1}, starting {2:yyyy-MM-dd HH:mm}",
                        dr[COL_FREQUENCE], Convert.ToInt32(dr[COL_FREQUENCE]) > 1 ? "s" : "", dr[COL_STARTDATE]);
                    break;
                case EventTypes.Monthly:
                    string monthlyMonths = GetMonthlyMonths(dr);
                    string monthlyType = GetMonthlyType(dr);
                    if (String.IsNullOrEmpty(monthlyMonths))
                    {
                        schedule = String.Empty;
                    }
                    else
                    {
                        schedule = String.Format("At {0:HH:mm} on {1} of {2}, starting {3:yyyy-MM-dd}",
                            dr[COL_STARTDATE], monthlyType, monthlyMonths, dr[COL_STARTDATE]);
                    }
                    break;
                default:
                    schedule = dr[COL_DESCRIPTION] as string;
                    break;
            }

            return schedule;
        }

        public static bool HasChanges
        {
            get
            {
                DataTable dt = _dtEvents.GetChanges();
                if (dt != null && dt.Rows.Count > 0)
                    return true;
                return false;
            }
        }

        public static void AcceptChanges()
        {            
            _dtEvents.AcceptChanges();
        }

        public static void Load()
        {
            _dsEvents = new DataSet(KEY_EVENTS);

            if (File.Exists(EVENTS_DB))
            {
                _dsEvents.ReadXml(EVENTS_DB, XmlReadMode.Auto);
                _dtEvents = _dsEvents.Tables[KEY_EVENTS];

                //2009-02-08: add COL_LASTLASTRUNTIME
                if(!_dtEvents.Columns.Contains(COL_LASTLASTRUNTIME)) 
                {
                    _dtEvents.Columns.Add(COL_LASTLASTRUNTIME, typeof(DateTime));
                }                
            }
            else
            {
                CreateEventsTable();
            }

            for (int i = 0; i < _dtEvents.Rows.Count; i++)
            {
                string schedule = _dtEvents.Rows[i][COL_SCHEDULE] as string;
                if (String.IsNullOrEmpty(schedule))
                {
                    _dtEvents.Rows[i][COL_SCHEDULE] = GetSchedule(_dtEvents.Rows[i]);
                }
            }

            AcceptChanges();
            _dtEvents.RowChanged += new DataRowChangeEventHandler(_dtEvents_RowChanged);
        }

        static bool _inRowChangedEvent = false;
        static void _dtEvents_RowChanged(object sender, DataRowChangeEventArgs e)
        {
            if (_inRowChangedEvent) return;
            if (e.Row.RowState == DataRowState.Deleted || 
                e.Row.RowState == DataRowState.Detached) 
                return;

            try
            {
                _inRowChangedEvent = true;
                e.Row[COL_SCHEDULE] = GetSchedule(e.Row);
            }
            catch
            {
                throw;
            }
            finally
            {
                _inRowChangedEvent = false;
            }
        }

        public static void Save()
        {
            if (!HasChanges)
                return;

            if (File.Exists(EVENTS_DB))
            {
                FileInfo fi = new FileInfo(EVENTS_DB);
                if (fi.Length > 0)
                {
                    if (File.Exists(EVENTS_DB_BAK))
                    {
                        File.Delete(EVENTS_DB_BAK);
                    }
                    File.Copy(EVENTS_DB, EVENTS_DB_BAK);
                }
            }
            
            AcceptChanges();

            using (FileStream fs = File.Open(EVENTS_DB, FileMode.Create, FileAccess.Write))
            {
                _dsEvents.WriteXml(fs, XmlWriteMode.WriteSchema);
            }
        }

        public static DataView GetEvents()
        {
            return GetEvents(EventStatus.Active);
        }

        public static DataView GetEvents(EventStatus es)
        {
            string filter = String.Format("{0}='{1}'", COL_STATUS, es.ToString());
            return new DataView(_dtEvents, filter, COL_NAME, DataViewRowState.CurrentRows);
        }

        public static DataView GetEvents(string id)
        {
            string filter = String.Format("{0}='{1}'", COL_ID, id);
            return new DataView(_dtEvents, filter, null, DataViewRowState.CurrentRows);
        }

        public static DataRow GetEvent(string id)
        {
            if (String.IsNullOrEmpty(id)) return null;
            DataView dv = GetEvents(id);
            if (dv.Count == 1) return dv[0].Row;
            return null;
        }

        public static void DeleteEvent(string id)
        {
            DataRow dr = GetEvent(id);
            DeleteEvent(dr);
        }

        public static void DeleteEvent(DataRow dr)
        {
            if (dr == null) return;
            _dtEvents.Rows.Remove(dr);
        }

        public static EventBase CreateEvent(DataRow dr, bool firstTime)
        {
            EventBase eb = null;
            EventTypes et = Utils.ParseEventType(dr[MyEvents.COL_TYPE] as string);

            switch (et)
            {
                case EventTypes.Once:
                    eb = new EventOnce(dr);
                    break;
                case EventTypes.Startup:
                    eb = new EventStartup(dr, firstTime);
                    break;
                case EventTypes.Secondly:
                    eb = new EventSecondly(dr);
                    break;
                case EventTypes.Minutely:
                    eb = new EventMinutely(dr);
                    break;
                case EventTypes.Daily:
                    eb = new EventDaily(dr);
                    break;
                case EventTypes.Hourly:
                    eb = new EventHourly(dr);
                    break;
                case EventTypes.Yearly:
                    eb = new EventYearly(dr);
                    break;
                case EventTypes.Monthly:
                    eb = new EventMonthly(dr);
                    break;
                case EventTypes.Weekly:
                    eb = new EventWeekly(dr);
                    break;
                default:
                    break;
            }

            return eb;
        }

        public class InvalidEventTypeException : ApplicationException
        {
            public InvalidEventTypeException()
                : base("Invalid Event Type")
            {
            }
        }

    }//end of class
}
